/*
 * In general, FP encourage using immutable types to keep code free of concurrency
 * issues. This is because, unlike Java, FP language has built-in mechanism of
 * instantiating object in a very CHEAP performance overhead way
 */

package helloscala.practise.chapter8

import helloscala.practise.ChapterEntry

object Chapter8Entry extends ChapterEntry {
  def runTests: Unit = {
    testLazyVal
  }

  /**
   * unlike function call, which value computed each time called, lazy val only
   * intialized once. lazy doesnot support var
   */
  def testLazyVal = {
    trait T2 {
      println("start initialize T2")
      val value: Int //not yet initialized
      lazy val inverse: Double = {println("initializing inverse"); 1.0/value}
      //println("T2: value=" + value + "; inverse=" + inverse) //this statement is not lazy
    }

    val o = new T2 {
      println("start initialize o")
      val value: Int = 5
    }

    println("O: value=" + o.value + "; inverse=" + o.inverse)
  }

  def testCallByName = {
    //simulate while loop
    def whileAwesome(awesome: => Boolean)(f: => Unit): Unit = { //or written as f:()=>Unit.
      if (awesome) { //value of awesome was computed each time reaches here
        f
        whileAwesome(awesome)(f) //won't become infinite loop since value of awesome will be computed each time in if(..) block
      }
    }

    var count = 0
    whileAwesome(count < 5) {
      println("Still Awesom")
      count += 1
    }
  }

  /**
   * implicit rules:
   * 1. old type don't have the calling method defined
   * 2. conversion method must be implicit
   * 3. conversion method must be within the scope of calling context / or defined in the scope's companion object
   * 4. conversion method of old_type<->new_type mapping must have one and only one in the scope
   * 5. implicit method must explicitly specified return type in all cases, or any subtle change can crash the system
   * 6. used carefully, 'cause it is very hard to debug if anything happen
   */
  def testImplicit = {
    //type conversion. noted in java, String is final and not possible to extend, with imlicit conversion we can
    //easily "extend" the features of String, without affect language type inheritance rule.
    case class FancyString(private val str: String) {//use case class to enjoy auto generated companion object
      def strVal: String = {"'" + str + "'"}
    }
    implicit def string2Fancy(s: String): FancyString = {FancyString(s)} //if current scope don't have, import it.
    println("jeff".strVal) //note here, java.lang.String doesn't have strVal() defined

    //to implement function with "default value" params
    def multiFactor(seed: Int)(factor1: Int)(implicit factor2: Int) = {
      seed * factor1 * factor2
    }
    implicit val factor2 = 1 //default value of factor2 is 1
    println(multiFactor(5)(2))
    println(multiFactor(5)(2)(3))
  }

  def testCurryFunc = {
    //native curry function
    def cat(s1: String)(s2: String): String = s1 + s2 // equivalent to def cat(s1: String) = (s2: String) => s1 + s2
    println(cat("Jeff")("Zhou"))

    //curry function converted from normal function
    val catNormal = (s1: String, s2: String) => s1 + s2
    val curryCat1 = catNormal.curried //style like Function.curried(catNormal _) was deprecated
    val curryCat2 = catNormal.curried("Jeff")
    println(curryCat1("Jeff")("Zhou"))
    println(curryCat2("Zhou"))

    val catMore = (s1: String, s2: String, s3: String) => s1 + s2 + s3
    val curryCatMore1 = catMore.curried("Jeff")
    println(curryCatMore1("Zhou")("Winnie")) //if ommit s2 or s3, becomes curryCatMore1("Winnie"), result becomes <function1>
  }

  def testPartialFunc = {
    //use normal func as partial
    def concateUpper(s1: String, s2: String): String = (s1 + " " + s2).toString.toUpperCase
    val c1 = concateUpper _
    val c2 = concateUpper("jeff", _: String)
    println(c1("jeff", "winnie")) //you might recall this is implicit apply() execution
    println(c2("winnie"))

    //built-in partial example
    List(1,2,3) foreach println

    //custom PartialFunction
    val truthier: PartialFunction[Boolean, String] = {case true => "Truth!"}
    val fallback: PartialFunction[Boolean, String] = {case x => "sketchy"}
    val tester = truthier orElse fallback
    println(tester(1 == 1))
    println(tester(1+1 == 3))
    println(tester.isDefinedAt(true))
  }

  def testOptions = {
    val oldList = List(Some(5), None)
    for (option <- oldList) {
      println(option)
      option map (n => println(n * 5)) //executed only when option ne None
                                       //so no extra pattern matching required
    }
  }

  def testCollections = {
    //traverse
    val mapData = Map("p1" -> "Jeff", "p2" -> "Winnie")
    mapData foreach {kv => println(kv._1 + " -> " + kv._2)}

    //mapping
    val map1 = mapData map {kv => (kv._1, kv._2.length)}
    println(mapData)
    println(map1)

    //filtering
    val map2 = mapData filter (kv => kv._1 endsWith("1"))
    println(mapData)
    println(map2)

    //reducing and folding
    val listData = List(1,2,3,4,5,6)
    println(listData.foldLeft(10) (_ * _))
    println(listData reduceLeft (_ * _))
    println(listData.foldRight(List[String]()) {
      (x, list) => "<" + x + ">" :: list
    })
    println(listData.foldLeft(List[String]()) {
      (list, x) => "<" + x + ">" :: list
    })
  }

  def testFunctionalDataStructure = {
    //native collections are immutable, any add/remove operation will create a new instance

    //performance cost of 'prepending' of a List is O(1), very cheap
    val list = "Cameron" :: List("Jeff", "Winnie") //because 'prepending', it is more natural than written in "List(..).::("Cameron")"
    println(list)

    val map1 = Map(1 -> "Jeff", 2 -> "Winnie") + Pair(3, "Cameron") //add Cameron pair
    val map2 = map1 - 1 //remove Jeff pair
    println(map2)

    val map3 = Map("p1" -> "Jeff", "p2" -> "Winnie") - "p1"
    println(map3)
  }

  //"tail" means, a resursive function calls itself as its 'final' operation
  def testTailRecursion = {
    def factorial(i: BigInt): BigInt = {
      def fact(i: BigInt, accumulator: BigInt): BigInt = i match {
        case _ if i == 1 => accumulator
        case _ => fact(i - 1, i * accumulator)
      }
      fact(i, 1)
      }

    for (i <- 1 to 10)
      print(format("%s: %s\n", i, factorial(i)))
  }

  def testRecursion2Loop = {
    //win speed, loss space. and free of risk of stack overflow
    def factorial_loop(start: Int): BigInt = {
      var result = BigInt(1)
      for (i <- 2 to start)
        result *= BigInt(i)

      result
    }
    for (i <- 1 to 10)
      print(format("%s: %s\n", i, factorial_loop(i)))

    println

    //win space, loss speed. risky for stack overflow with large 'start'
    def factorial_recurse(start: Int): BigInt = start match {
      case _ if start == 1 => BigInt(start)
      case _ => BigInt(start) * factorial_recurse(start - 1) //it is not 'tail'
        //because it produce the result by multiply start 'after' calling itself
    }
    for (i <- 1 to 10)
      print(format("%s: %s\n", i, factorial_recurse(i)))

  }

  //principle of func prog is let scope context transparent to function
  def testContextFactor = {
    var factor = 3

    def multiplier(i: Int) = i * factor

    println(List(1,2,3,4,5) map multiplier)

    factor = 5 //once context "factor" changed, behavior of multiplier also changed

    println(List(1,2,3,4,5) map multiplier)
  }

  def testIteration = {
    println(List(1,2,3,4,5) map (_ * 2))

    //The first _ represents the argument that is accumulating the value of
    //the reduction and the second _ represents the current element of the list.
    println(List(1,2,3,4,5) reduceLeft {_ * _})
  }
}
